use std::collections::VecDeque;

use chrono::{ DateTime, Local, Utc };
use fudata::{ model::{ kline::Kline, security::SecurityPerformance }, round2 };
use log::{ error, info, trace };

/// Calculates the slope of a linear regression line for a set of closing prices.
///
/// # Arguments
/// * `closing_prices` - A vector of closing prices.
///
/// # Returns
/// * The slope of the regression line.
fn calculate_slope(closing_prices: &[f64]) -> f64 {
    let n = closing_prices.len() as f64;
    let sum_x = (0..n as usize).map(|i| i as f64).sum::<f64>();
    let sum_y = closing_prices.iter().copied().sum::<f64>();
    let sum_xy = (0..n as usize)
        .zip(closing_prices.iter().copied())
        .map(|(i, y)| (i as f64) * y)
        .sum::<f64>();
    let sum_x_squared = (0..n as usize).map(|i| (i as f64).powi(2)).sum::<f64>();

    let numerator = n * sum_xy - sum_x * sum_y;
    let denominator = n * sum_x_squared - sum_x.powi(2);

    if denominator == 0.0 {
        0.0 // Avoid division by zero
    } else {
        numerator / denominator
    }
}

/// 统计阳多阴少比率
pub fn calculate_updn_price_ratio(
    all_klines: &Vec<Kline>,
    end_klts: &DateTime<Utc>,
    span_day: i64
) -> f64 {
    let start_time = Local::now();
    if all_klines.is_empty() {
        return 0.0;
    }

    let start_klts = end_klts.clone() - chrono::Duration::days(span_day);

    // Filter the Kline objects based on the klts being within the date range
    let klines: Vec<&Kline> = all_klines
        .iter()
        .filter(|k| start_klts <= k.klts && k.klts < end_klts.clone())
        .collect();

    // Initialize counters and weights
    let total_count = klines.len(); // 总的 K 线数量
    let mut up_weights = 0.0;
    let mut down_weights = 0.0;
    let mut total_weights = 0.0;

    for (index, kline) in klines.iter().enumerate() {
        let weight = (total_count as f64) - (index as f64); // 位置较靠后的序列具有更高的权重

        if kline.close > kline.open {
            // 阳线

            up_weights += weight;
        } else if kline.close < kline.open {
            // 阴线

            down_weights += weight;
        }

        total_weights += weight;
    }

    if total_weights == 0.0 {
        0.0
    } else {
        let up_ratio = up_weights / total_weights;
        let down_ratio = down_weights / total_weights;

        // 返回阳线与阴线的比率
        round2(up_ratio / down_ratio)
    }
}

/// 统计阳放阴收比率
pub fn calculate_updn_volume_ratio(
    all_klines: &Vec<Kline>,
    end_klts: &DateTime<Utc>,
    span_day: i64
) -> f64 {
    let start_time = Local::now();
    if all_klines.is_empty() {
        return 0.0;
    }

    let start_klts = end_klts.clone() - chrono::Duration::days(span_day);

    // Filter the Kline objects based on the klts being within the date range
    let klines: Vec<&Kline> = all_klines
        .iter()
        .filter(|k| start_klts <= k.klts && k.klts < end_klts.clone())
        .collect();

    // 统计阳聚阴散比率, 其中位置较靠后的上涨/下跌序列具有更高的权重。
    // Initialize counters and weights
    let total_count = klines.len(); // 总的 K 线数量
    let mut up_weights = 0.0;
    let mut down_weights = 0.0;
    let mut total_weights = 0.0;
    let mut up_streak = 0;
    let mut down_streak = 0;

    for (index, kline) in klines.iter().enumerate() {
        let weight = (total_count as f64) - (index as f64); // 位置较靠后的序列具有更高的权重

        if kline.close > kline.open {
            // 阳线
            up_streak += 1;
            down_streak = 0; // Reset the down streak when encountering an up line
            up_weights += weight * (up_streak as f64) * (kline.volume as f64); // Increase the weight based on the streak
        } else if kline.close < kline.open {
            // 阴线
            down_streak += 1;
            up_streak = 0; // Reset the up streak when encountering a down line
            down_weights += weight * (down_streak as f64) * (kline.volume as f64); // Increase the weight based on the streak
        } else {
            // Close equals Open (do nothing)
            up_streak = 0;
            down_streak = 0;
        }

        total_weights += weight * (up_streak.max(down_streak) as f64) * (kline.volume as f64); // Add the maximum streak weight to the total weight
    }

    if total_weights == 0.0 {
        0.0
    } else {
        let up_ratio = up_weights / total_weights;
        let down_ratio = down_weights / total_weights;

        // 返回阳线与阴线的比率
        round2(up_ratio / down_ratio)
    }
}

/// 统计阳聚阴散比率
pub fn calculate_updn_consecutive_ratio(
    all_klines: &Vec<Kline>,
    end_klts: &DateTime<Utc>,
    span_day: i64
) -> f64 {
    let start_time = Local::now();
    if all_klines.is_empty() {
        return 0.0;
    }

    let start_klts = end_klts.clone() - chrono::Duration::days(span_day);

    // Filter the Kline objects based on the klts being within the date range
    let klines: Vec<&Kline> = all_klines
        .iter()
        .filter(|k| start_klts <= k.klts && k.klts < end_klts.clone())
        .collect();

    // 统计阳聚阴散比率, 其中位置较靠后的上涨/下跌序列具有更高的权重。
    // Initialize counters and weights
    let total_count = klines.len(); // 总的 K 线数量
    let mut up_weights = 0.0;
    let mut down_weights = 0.0;
    let mut total_weights = 0.0;
    let mut up_streak = 0;
    let mut down_streak = 0;

    for (index, kline) in klines.iter().enumerate() {
        let weight = (total_count as f64) - (index as f64); // 位置较靠后的序列具有更高的权重

        if kline.close > kline.open {
            // 阳线
            up_streak += 1;
            down_streak = 0; // Reset the down streak when encountering an up line
            up_weights += weight * (up_streak as f64); // Increase the weight based on the streak
        } else if kline.close < kline.open {
            // 阴线
            down_streak += 1;
            up_streak = 0; // Reset the up streak when encountering a down line
            down_weights += weight * (down_streak as f64); // Increase the weight based on the streak
        } else {
            // Close equals Open (do nothing)
            up_streak = 0;
            down_streak = 0;
        }

        total_weights += weight * (up_streak.max(down_streak) as f64); // Add the maximum streak weight to the total weight
    }

    if total_weights == 0.0 {
        0.0
    } else {
        let up_ratio = up_weights / total_weights;
        let down_ratio = down_weights / total_weights;

        // 返回阳线与阴线的比率
        round2(up_ratio / down_ratio)
    }
}

/// 计算一定时间跨度内大涨次数
pub fn calculate_upstops_count(
    all_klines: &Vec<Kline>,
    end_klts: &DateTime<Utc>,
    span_day: &i64
) -> u16 {
    let start_time = Local::now();
    if all_klines.is_empty() {
        return 0;
    }

    let start_klts = end_klts.clone() - chrono::Duration::days(*span_day);

    // Filter the Kline objects based on the klts being within the date range
    let klines: Vec<&Kline> = all_klines
        .iter()
        .filter(|k| start_klts <= k.klts && k.klts < end_klts.clone())
        .collect();

    let mut count = 0;
    for kline in klines {
        if kline.frate > 7.0 {
            count += 1;
        }
    }

    let spent_time = Local::now().signed_duration_since(start_time);
    trace!(
        "compute_upstops_count  spent seconds={}.{} ",
        spent_time.num_seconds(),
        spent_time.num_milliseconds() % 1000
    );
    count
}

/// Calculates the standard deviation (volatility) of closing prices.
///
/// 虽然振幅可以作为一个简单的指标来衡量价格波动范围，但在金融分析中，标准差通常被认为是一个更为准确的波动性度量。
/// 因此，如果你需要更精确地衡量股票的波动性，建议使用标准差。
///
/// # Arguments
/// * `klines` - A vector of Kline records.
///
/// # Returns
/// * `f64` - The standard deviation of closing prices.
/// Calculates the standard deviation (volatility) of closing prices for a specified time span.
///
/// # Arguments
/// * `all_klines` - A vector of references to Kline records.
/// * `before_klts` - A reference to the DateTime before which to select Kline records.
/// * `span_day` - The number of days before `before_klts` to include in the calculation.
///
/// # Returns
/// * `f64` - The standard deviation of closing prices.
pub fn calculate_volatility(
    all_klines: &Vec<Kline>,
    end_klts: &DateTime<Utc>,
    span_day: i64
) -> f64 {
    let start_klts = end_klts.clone() - chrono::Duration::days(span_day);

    // Filter klines that are within the specified span
    let filtered_klines: Vec<&Kline> = all_klines
        .iter()
        .filter(|k| start_klts <= k.klts && k.klts < end_klts.clone())
        .collect();

    if filtered_klines.is_empty() {
        return 0.0;
    }

    let n = filtered_klines.len() as f64;
    let sum_closes: f64 = filtered_klines
        .iter()
        .map(|k| k.close)
        .sum();
    let mean_close = sum_closes / n;

    let variance: f64 =
        filtered_klines
            .iter()
            .map(|k| (k.close - mean_close).powi(2))
            .sum::<f64>() / n;

    round2(variance.sqrt() * 100.0)
}

/// Calculates a security performance for a set of Kline records based on volume, turnover rate, and volatility over a given period.
///
/// # Arguments
/// * `klines` - A reference to a vector of Kline records.
/// * `span_days` - The number of days to use for calculating volume, turnover rate, and volatility.
/// * `before_klts` - A reference to the DateTime before which to select Kline records.
///
/// # Returns
/// * `SecurityPerfoStockPerformancermance` - The security performance.
pub fn calculate_security_performance(
    all_klines: &Vec<Kline>,
    span_day: i64,
    end_klts: &DateTime<Utc>
) -> Option<SecurityPerformance> {
    let start_klts = end_klts.clone() - chrono::Duration::days(span_day);

    if let Some(fst_kline) = all_klines.first() {
        let security_id = &fst_kline.security_id;

        // Filter klines that are within the specified span
        let filtered_klines: Vec<&Kline> = all_klines
            .iter()
            .filter(|k| start_klts <= k.klts && k.klts < end_klts.clone())
            .collect();

        let first_days = Utc::now().signed_duration_since(fst_kline.klts).num_days();

        let total_value: f64 = filtered_klines
            .iter()
            .map(|k| k.value / 100000000.0)
            .sum();
        let average_value = round2((total_value as f64) / (filtered_klines.len() as f64));

        let total_trate: f64 = filtered_klines
            .iter()
            .map(|k| k.trate)
            .sum();
        let average_trate = round2(total_trate / (filtered_klines.len() as f64));

        let total_up_frate: f64 = filtered_klines
            .iter()
            .filter(|k| k.frate > 0.0)
            .map(|k| k.frate)
            .sum();

        let total_up_frate_count: f64 = filtered_klines
            .iter()
            .filter(|k| k.frate > 0.0)
            .map(|_| 1.0)
            .sum();
        let average_up_frate = round2(total_up_frate / total_up_frate_count);

        let total_dn_frate: f64 = filtered_klines
            .iter()
            .filter(|k| k.frate < 0.0)
            .map(|k| k.frate)
            .sum();

        let total_dn_frate_count: f64 = filtered_klines
            .iter()
            .filter(|k| k.frate < 0.0)
            .map(|_| 1.0)
            .sum();
        let average_dn_frate = round2(total_dn_frate / total_dn_frate_count);

        let volatility = calculate_volatility(all_klines, end_klts, span_day);
        // 涨幅>7%的次数
        let upstops_count = calculate_upstops_count(all_klines, end_klts, &span_day) as f64;

        // 三阳控三阴
        // 1. 阳多阴少
        let updn_price_ratio = calculate_updn_price_ratio(all_klines, end_klts, span_day);
        // 2. 阳放阴收
        let updn_volume_ratio = calculate_updn_volume_ratio(all_klines, end_klts, span_day);
        // 3. 阳聚阴散，也就是上涨连续性与下跌连续性对比
        let updn_consecutive_ratio = calculate_updn_consecutive_ratio(
            all_klines,
            end_klts,
            span_day
        );

        // Weighted sum as the activity score

        let activity_score = round2(
            3.0 * average_value +
                3.0 * average_trate +
                2.0 * volatility +
                2.0 * upstops_count +
                5.0 * (updn_price_ratio + updn_volume_ratio + updn_consecutive_ratio)
        );

        let result = Some(SecurityPerformance {
            security_id: security_id.to_owned(),
            first_days,
            span_day,
            average_value,
            average_trate,
            average_up_frate,
            average_dn_frate,
            volatility,
            upstops_count,
            updn_price_ratio,
            updn_volume_ratio,
            updn_consecutive_ratio,
            activity_score,
        });
        info!("security_id:{} {:?}", security_id, activity_score);
        result
    } else {
        None
    }
}

pub struct Beauty {
    pub code: String,
    pub name: String,
    pub neck_price: f64,
    pub head_price: f64,
    // computed fields
    pub buy_price: f64,
    pub object_price: f64,
    pub stop_price: f64,
    pub stop_amount: f64,
    pub pl_ratio: f64,
    pub strong_host: bool,
}

impl Beauty {
    /// 创建📈上涨美女
    pub fn new_up(code: &str, name: &str, neck_price: f64, head_price: f64) -> Beauty {
        let mut buy_price = round2(neck_price * 1.03);
        let object_price = round2(neck_price - head_price + neck_price);
        let pl_ratio = 15.0;
        let mut return_ratio = round2((neck_price - head_price) / (buy_price - neck_price));
        let mut strong_host = true;

        if return_ratio < pl_ratio {
            buy_price = round2((neck_price - head_price + neck_price * pl_ratio) / pl_ratio);
            return_ratio = pl_ratio;
            strong_host = false;
        }
        let stop_price = neck_price;
        let stop_amount = round2(buy_price - stop_price);

        Beauty {
            code: code.to_string(),
            name: name.to_string(),
            neck_price,
            head_price,
            buy_price,
            object_price,
            stop_price,
            stop_amount,
            pl_ratio: return_ratio,
            strong_host,
        }
    }
}

#[cfg(test)]
mod test {
    use std::cmp::Ordering;

    use chrono::{ Duration, TimeZone, Utc };
    use fudata::{
        db,
        model::{ kline::{ Kline, Klt }, security::{ self, Market, Security } },
        round2,
    };
    use surrealdb::sql::Thing;
    use yata::methods::LinReg;

    use crate::qtrade::strategy::util::stats::{
        calculate_security_performance,
        calculate_slope,
        calculate_updn_consecutive_ratio,
        calculate_volatility,
        Beauty,
    };
    use yata::core::Method;

    #[tokio::test]
    async fn test_calculate_slope() {
        let closing_prices = vec![10.0, 25.0, 30.0, 55.0, 120.0];
        let slope = calculate_slope(&closing_prices);
        println!("Slope: {}", slope);
    }

    #[tokio::test]
    async fn test_linreg_vs_slope() {
        let closing_prices = vec![100.0, 105.0, 110.0, 115.0, 120.0];
        let length = 5;

        let mut linreg = LinReg::new(length, &closing_prices[0]).unwrap();
        for price in closing_prices.iter() {
            linreg.next(price);
        }

        let slope_direct = calculate_slope(&closing_prices);
        let slope_tan = linreg.tan();

        println!("Direct slope: {}", slope_direct);
        println!("Tan slope: {}", slope_tan);
    }

    #[tokio::test]
    async fn test_calculate_updn_consecutive_ratio() {
        // Initialize the logger for the test context.
        // pretty_env_logger::try_init().unwrap();
        pretty_env_logger::init_timed();
        let _ = db::init().await.unwrap();

        let security_id = "SZ.STK.300363";
        let all_klines = db::kline
            ::select(security_id, None, Some(&Klt::DAY), Some("klts DESC"), 0, 250).await
            .unwrap();
        let now = Utc::now();
        let ratio = calculate_updn_consecutive_ratio(&all_klines, &now, 60);

        println!("Up/Dn Consecutive Ratio: {}", ratio);
    }

    #[tokio::test]
    async fn test_calculate_security_performance() {
        // Initialize the logger for the test context.
        // pretty_env_logger::try_init().unwrap();
        pretty_env_logger::init();
        let _ = db::init().await.unwrap();

        let securities;
        if 1 == 2 {
            let all_securities = db::security
                ::select(
                    &Some(Market::SH),
                    Some(security::SecurityType::STK),
                    None,
                    Some("id ASC"),
                    0,
                    3000 //MAX_STK_SECURITY
                ).await
                .unwrap();
            // 使用迭代器过滤出 security_id 以 "SH.SZ.60" 开头的对象,如果您想要拥有所有权而非引用：
            securities = all_securities
                .into_iter()
                .filter(|security| {
                    let security_id = security.id_raw();
                    security_id.starts_with("SH.STK.60") ||
                        security_id.starts_with("SZ.STK.30") ||
                        security_id.starts_with("SZ.STK.00")
                })
                .collect();
        } else {
            securities = vec![
                Security {
                    id: Some(Thing::from(("security", "SH.STK.600187"))),
                    ..Default::default()
                },
                Security {
                    id: Some(Thing::from(("security", "SH.STK.600838"))),
                    ..Default::default()
                },
                Security {
                    id: Some(Thing::from(("security", "SH.STK.603138"))),
                    ..Default::default()
                },
                Security {
                    id: Some(Thing::from(("security", "SH.STK.600550"))),
                    ..Default::default()
                }
            ];
        }

        let mut stock_perfs = Vec::new();
        // 最近1年内股票活跃性评分
        let span_day = 6 * 31;
        for security in securities.iter() {
            let security_id = security.id_raw();

            let mut all_klines = db::kline
                ::select(
                    security_id.as_str(),
                    None,
                    Some(&Klt::DAY),
                    Some("klts DESC"),
                    0,
                    2500
                ).await
                .unwrap();
            all_klines.reverse();
            let end_klts = Utc.with_ymd_and_hms(2024, 9, 22, 0, 0, 0).unwrap();

            let activity_score = calculate_security_performance(&all_klines, span_day, &end_klts);
            if let Some(perf) = activity_score {
                stock_perfs.push(perf);
            }
        }

        stock_perfs.sort_by(|a, b| {
            b.activity_score.partial_cmp(&a.activity_score).unwrap_or(std::cmp::Ordering::Equal)
        });

        println!("最近{:?}日内股票活跃排行", span_day);
        println!(
            "#, security_id, first_days, span_day, activity_score, average_up_frate, average_dn_frate, average_diff_frate, average_value, average_trate, volatility, upstops_count, updn_price_ratio, updn_volume_ratio, updn_consecutive_ratio,  "
        );
        for (idx, stock) in stock_perfs.iter().enumerate() {
            println!(
                "{idx}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}",
                stock.security_id,
                stock.first_days,
                stock.span_day,
                stock.activity_score,
                stock.average_up_frate,
                stock.average_dn_frate,
                stock.average_diff_frate(),
                stock.average_value,
                stock.average_trate,
                stock.volatility,
                stock.upstops_count,
                stock.updn_price_ratio,
                stock.updn_volume_ratio,
                stock.updn_consecutive_ratio
            );
        }
    }

    #[tokio::test]
    async fn test_calculate_volatility() {
        // Initialize the logger for the test context.
        // pretty_env_logger::try_init().unwrap();
        pretty_env_logger::init_timed();
        let _ = db::init().await.unwrap();

        let now = Utc::now();
        // 获取前一天的日期时间
        let day_before = now - Duration::days(1250);
        println!(" day_before from {}", day_before);

        let all_klines = db::kline
            ::select("SH.STK.600839", None, Some(&Klt::DAY), Some("klts DESC"), 0, 1250).await
            .unwrap();
        println!("select oldest kline={:?}", all_klines.last());

        let mut min = f64::MAX;
        let mut max = f64::MIN;
        let span_day = 12 * 31;
        for (idx, curr_kl) in all_klines.iter().enumerate() {
            if 0 <= idx && idx < all_klines.len() {
                let volatility = calculate_volatility(&all_klines, &curr_kl.klts, span_day);
                println!("[{idx}] {:?} Volatility: {:.2}", curr_kl.id_raw(), volatility);
                if volatility > max {
                    max = volatility;
                }
                if volatility < min {
                    min = volatility;
                }
            }
        }
        println!("max= {max} min= {min}");
    }

    #[tokio::test]
    async fn test_caculate_up_beauty_girl() {
        let mut beauties = vec![
            Beauty::new_up("金刚光伏", "300091", 20.6, 9.81),
            Beauty::new_up("金刚光伏", "300091", 24.18, 9.81),
            Beauty::new_up("庚星股份", "600753", 7.99, 4.31),
            Beauty::new_up("长药控股", "300391", 6.27, 2.24),
            Beauty::new_up("七彩化学", "300758", 15.93, 5.32),
            Beauty::new_up("志特新材", "300986", 13.3, 5.52),
            Beauty::new_up("振东制药", "300158", 5.46, 3.13),
            Beauty::new_up("博腾股份", "300363", 21.03, 10.91),
            Beauty::new_up("博腾股份", "300363", 17.81, 10.91),
            Beauty::new_up("晶澳科技", "002459", 15.95, 8.96),
            Beauty::new_up("汇顶科技", "603160", 72.8, 44.18),
            Beauty::new_up("韦尔股份", "603501", 115.75, 75.8),
            Beauty::new_up("士兰微", "600460", 27.85, 15.74),
            Beauty::new_up("国脉文化", "600640", 13.48, 8.96),
            Beauty::new_up("钧达股份", "002865", 58.15, 34.0),
            Beauty::new_up("TCL中环", "002129", 11.66, 7.35),
            Beauty::new_up("爱旭股份", "600732", 9.96, 7.36),
            Beauty::new_up("光伏设备", "881279", 15.95, 8.96),
            Beauty::new_up("协鑫集成", "002506", 2.48, 1.73),
            Beauty::new_up("远大控股", "000626", 5.79, 3.8),
            Beauty::new_up("通威股份", "600438", 27.23, 15.79),
            Beauty::new_up("宇邦新材", "301266", 36.6, 26.96),
            Beauty::new_up("川润股份", "002272", 6.09, 4.02),

            Beauty::new_up("派瑞股份", "300831", 17.54, 6.49),
            Beauty::new_up("香雪制药", "300147", 9.79, 3.0),
            Beauty::new_up("凤凰光学", "600071", 27.26, 14.21),
            Beauty::new_up("通威股份", "600438", 27.02, 15.79),
            Beauty::new_up("华闻集团", "000793", 2.59, 0.7)
        ];

        beauties.sort_by(|a, b|
            b.pl_ratio.partial_cmp(&a.pl_ratio).unwrap_or(std::cmp::Ordering::Equal)
        );
        println!(
            "#追涨美女📈,code, name, 颈线价, 头部价, 买入价, 止损价, 止损额, 目标价, 盈亏比, 强庄"
        );

        for (idx, beauty) in beauties.iter().enumerate() {
            println!(
                "{idx},{},{},{},{},{},{},{},{},{},{}",
                beauty.code,
                beauty.name,
                beauty.neck_price,
                beauty.head_price,
                beauty.buy_price,
                beauty.stop_price,
                beauty.stop_amount,
                beauty.object_price,
                beauty.pl_ratio,
                beauty.strong_host
            );
        }
    }

    /// 抄底美女📉
    #[tokio::test]
    async fn test_caculate_dn_beauty_girl() {
        let neck_price = 46.61;
        let head_price = 68.13;

        let mut buy_price = round2(neck_price - (head_price - neck_price));
        let object_price = neck_price;
        let pl_ratio = 15.0;

        let mut return_ratio = round2((head_price - neck_price) / (buy_price * 0.03));

        if return_ratio < pl_ratio {
            buy_price = round2((neck_price - head_price) / pl_ratio / 0.03);
            return_ratio = pl_ratio;
        }

        let stop_price = buy_price * 0.97;

        println!(
            "抄底美女📉\n颈线价: {} \n头部价: {} \n买入价:{} \n止损价:{} \n目标价:{} \n盈亏比:{}",
            neck_price,
            head_price,
            buy_price,
            stop_price,
            object_price,
            return_ratio
        )
    }
}
